
from direct.directnotify.DirectNotifyGlobal import directNotify
from direct.distributed.DistributedObjectBase import DistributedObjectBase
from direct.showbase.MessengerGlobal import messenger

#from PyDatagram import PyDatagram
#from PyDatagramIterator import PyDatagramIterator

# Values for DistributedObjectOV.activeState
# these should match DistributedObject.ES*

ESNew          = 1
ESDeleted      = 2
ESDisabling    = 3
ESDisabled     = 4  # values here and lower are considered "disabled"
ESGenerating   = 5  # values here and greater are considered "generated"
ESGenerated    = 6

class DistributedObjectOV(DistributedObjectBase):
    """
    Implementation of the 'owner view' (OV) of a distributed object;
    """
    notify = directNotify.newCategory("DistributedObjectOV")

    def __init__(self, cr):
        assert self.notify.debugStateCall(self)
        if not hasattr(self, 'DistributedObjectOV_initialized'):
            self.DistributedObjectOV_initialized = 1
            DistributedObjectBase.__init__(self, cr)

            # Keep track of our state as a distributed object.  This
            # is only trustworthy if the inheriting class properly
            # calls up the chain for disable() and generate().
            self.activeState = ESNew

    if __debug__:
        def status(self, indent=0):
            """
            print out "doId(parentId, zoneId) className"
                and conditionally show generated, disabled
            """
            spaces = ' ' * (indent + 2)
            try:
                print("%s%s:" % (' ' * indent, self.__class__.__name__))

                flags = []
                if self.activeState == ESGenerated:
                    flags.append("generated")
                if self.activeState < ESGenerating:
                    flags.append("disabled")

                flagStr = ""
                if len(flags) > 0:
                    flagStr = " (%s)" % (" ".join(flags))

                print("%sfrom DistributedObjectOV doId:%s, parent:%s, zone:%s%s" % (
                    spaces, self.doId, self.parentId, self.zoneId, flagStr))
            except Exception as e:
                print("%serror printing status %s" % (spaces, e))


    def getDelayDeleteCount(self):
        # OV objects cannot be delayDeleted
        return 0

    def deleteOrDelay(self):
        self.disableAnnounceAndDelete()

    def disableAnnounceAndDelete(self):
        self.disableAndAnnounce()
        self.delete()

    def disableAndAnnounce(self):
        # We must send the disable announce message *before* we
        # actually disable the object.  That way, the various cleanup
        # tasks can run first and take care of restoring the object to
        # a normal, nondisabled state; and *then* the disable function
        # can properly disable it (for instance, by parenting it to
        # hidden).
        if self.activeState != ESDisabled:
            self.activeState = ESDisabling
            messenger.send(self.uniqueName("disable"))
            self.disable()

    def announceGenerate(self):
        """
        Sends a message to the world after the object has been
        generated and all of its required fields filled in.
        """
        assert self.notify.debug('announceGenerate(): %s' % (self.doId))

    def disable(self):
        """
        Inheritors should redefine this to take appropriate action on disable
        """
        assert self.notify.debug('disable(): %s' % (self.doId))
        if self.activeState != ESDisabled:
            self.activeState = ESDisabled

    def isDisabled(self):
        """
        Returns true if the object has been disabled and/or deleted,
        or if it is brand new and hasn't yet been generated.
        """
        return self.activeState < ESGenerating

    def isGenerated(self):
        """
        Returns true if the object has been fully generated by now,
        and not yet disabled.
        """
        assert self.notify.debugStateCall(self)
        return self.activeState == ESGenerated

    def delete(self):
        """
        Inheritors should redefine this to take appropriate action on delete
        """
        assert self.notify.debug('delete(): %s' % (self.doId))
        if not hasattr(self, 'DistributedObjectOV_deleted'):
            self.DistributedObjectOV_deleted = 1
            self.cr = None
            self.dclass = None

    def generate(self):
        """
        Inheritors should redefine this to take appropriate action on generate
        """
        assert self.notify.debugStateCall(self)
        self.activeState = ESGenerating
        # this has already been set at this point
        #self.cr.storeObjectLocation(self, self.parentId, self.zoneId)

    def generateInit(self):
        """
        This method is called when the DistributedObjectOV is first introduced
        to the world... Not when it is pulled from the cache.
        """
        self.activeState = ESGenerating

    def getDoId(self):
        """
        Return the distributed object id
        """
        return self.doId

    def postGenerateMessage(self):
        if self.activeState != ESGenerated:
            self.activeState = ESGenerated
            messenger.send(self.uniqueName("generate"), [self])


    def updateRequiredFields(self, dclass, di):
        dclass.receiveUpdateBroadcastRequired(self, di)
        self.announceGenerate()
        self.postGenerateMessage()

    def updateAllRequiredFields(self, dclass, di):
        dclass.receiveUpdateAllRequired(self, di)
        self.announceGenerate()
        self.postGenerateMessage()

    def updateRequiredOtherFields(self, dclass, di):
        # First, update the required fields
        dclass.receiveUpdateBroadcastRequiredOwner(self, di)

        # Announce generate after updating all the required fields,
        # but before we update the non-required fields.
        self.announceGenerate()
        self.postGenerateMessage()

        dclass.receiveUpdateOther(self, di)

    def getCacheable(self):
        return False

    def sendUpdate(self, fieldName, args = [], sendToId = None):
        if self.cr:
            dg = self.dclass.clientFormatUpdate(
                fieldName, sendToId or self.doId, args)
            self.cr.send(dg)
        else:
            self.notify.warning("sendUpdate failed, because self.cr is not set")

    def taskName(self, taskString):
        return '%s-%s-OV' % (taskString, self.getDoId())

    def uniqueName(self, idString):
        return '%s-%s-OV' % (idString, self.getDoId())
